/**
 * 
 */
package com.sebscape.sebcms.persistence.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sebscape.sebcms.persistence.CmsPersistenceException;
import com.sebscape.sebcms.persistence.PersistenceManagerFactoryWrapper;
import com.sebscape.sebcms.persistence.domain.IPersistedObject;
import com.sebscape.sebcms.persistence.domain.IUpdateable;

/**
 * @author Stephen
 *
 */
public class GenericDao<T, ID extends Serializable> implements IGenericDao<T, ID>{
	
	private static Log log = LogFactory.getLog( GenericDao.class );
	
	protected Class<T> persistentClass;
	
	/**
	 * Constructor for instances of this class (not derived classes).
	 * @param clazz
	 */
	public GenericDao( Class<T> clazz ){
		this.persistentClass = clazz;
	}

	/**
	 * This constructor is only available to subclasses which specify
	 * the generics, ie:<br/>
	 *   class myDao extends GenericDao<MyClass, Long>
	 */
	@SuppressWarnings("unchecked")
	protected GenericDao(){
		this.persistentClass = (Class<T>)( (ParameterizedType) getClass().getGenericSuperclass() ).getActualTypeArguments()[0];
	}
	
	public PersistenceManager getPersistenceManager(){
		return PersistenceManagerFactoryWrapper.get().getPersistenceManager();
	}
	
	/**
	 * Saves any Object which implements persistedObject.
	 * @param persistedObj
	 * @throws CmsPersistenceException
	 */
	public T save( T persistedObj ){
		if( persistedObj != null ){
			PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
			Transaction tx = pm.currentTransaction();
			try {
				tx.begin();
				log.debug( "persisting " + persistedObj.getClass().getName() );
				pm.makePersistent( persistedObj );
				tx.commit();
			} catch (Exception e) {
				tx.rollback();
				e.printStackTrace();
				//throw new CmsPersistenceException( e.getCause().getMessage(), persistedObj );
			} finally {
				pm.close();
			}
		}
		return persistedObj;
	}

	/**
	 * Saves any Object which implements persistedObject.
	 * @param persistedObj
	 * @throws CmsPersistenceException
	 */
	@SuppressWarnings("unchecked")
	public T update( T persistedObj ){
		if( persistedObj != null ){
			if( ((IPersistedObject)persistedObj).getId() == null ){
				return save( persistedObj );
			}
			PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
			Transaction tx = pm.currentTransaction();
			try {
				tx.begin();
				// load the original object.
				T item = (T)pm.getObjectById( persistedObj.getClass(), ((IPersistedObject)persistedObj).getId() );
				( (IUpdateable)item ).update( persistedObj );
				log.debug( "persisting " + persistedObj.getClass().getName() );
				pm.makePersistent( item );
				tx.commit();
			} catch (Exception e) {
				e.printStackTrace();
				tx.rollback();
				//throw new CmsPersistenceException( e.getCause().getMessage(), persistedObj );
			} finally {
				pm.close();
			}
		}
		return persistedObj;
	}
	
	/**
	 * Allow Objects to create and submit their own JDO-QL.
	 */
	public Object query( String jdoql ){
		if( jdoql != null ){
			PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
			Transaction tx = pm.currentTransaction();
			try {
				tx.begin();
				log.debug( "firing jdoql: " + jdoql );
				Object result = pm.newQuery( jdoql ).execute();
				return result;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				//throw new CmsPersistenceException( e.getCause().getMessage() + " jdoQL: " + jdoql );
			} finally {
				tx.commit();
				pm.close();
			}
		}
		return null;
	}
	
	/**
	 * Find object by its unique id.
	 * @param id
	 * @return
	 * @throws CmsPersistenceException
	 */
	public T load( ID id ) {
		if(  id != null ){
//			String jdoQl = "select from " + persistentClass.getName() + " where id == " + id;
//			return (T)((List)this.query( jdoQl )).get( 0 );
			PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
			Transaction tx = pm.currentTransaction();
			try {
				tx.begin();
				return pm.detachCopy( pm.getObjectById( persistentClass, id ) );
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				//throw new CmsPersistenceException( e.getCause().getMessage() + " jdoQL: " + jdoql );
			} finally {
				tx.commit();
				pm.close();
			}

		}
		return null;
	}
	
	/**
	 * Loads the object by id, then deletes it.
	 * @param id
	 * @return
	 */
	public Boolean delete( ID id ){
		T object = this.load(id);
		if( object != null ){
			return this.delete( object );
		} else {
			return Boolean.TRUE;
		}
	}

	/* (non-Javadoc)
	 * @see com.sebscape.sebcms.persistence.IGenericDao#delete(java.lang.Object)
	 */
	@Override
	public Boolean delete(T persistedObject) {
		if( persistedObject != null ){
			PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
			try {
				pm.flush();
				Transaction tx = pm.currentTransaction();
				tx.begin();
				pm.deletePersistent( persistedObject );
				tx.commit();
				return Boolean.TRUE;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				//throw new CmsPersistenceException( e.getCause().getMessage(), persistedObj );
			} finally {
				pm.close();
			}
		}
		return Boolean.FALSE;
	}

	/* (non-Javadoc)
	 * @see com.sebscape.sebcms.persistence.IGenericDao#findAll()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<T> findAll() {
		PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
		Transaction tx = pm.currentTransaction();
		try {
			tx.begin();
			List<T> result = (List<T>)pm.newQuery( persistentClass ).execute();
			return result;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//throw new CmsPersistenceException( e.getCause().getMessage() + " jdoQL: " + jdoql );
		} finally {
			tx.commit();
			pm.close();
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see com.sebscape.sebcms.persistence.IGenericDao#findAll()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByFilter( String filter ) {
		PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
		Transaction tx = pm.currentTransaction();
		try {
			tx.begin();
			List<T> result = (List<T>)pm.newQuery( persistentClass, filter ).execute();
			return new ArrayList<T>( pm.detachCopyAll( result ) );
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//throw new CmsPersistenceException( e.getCause().getMessage() + " jdoQL: " + jdoql );
		} finally {
			tx.commit();
			pm.close();
		}
		return null;
	}
	
	/* (non-Javadoc)
	 * @see com.sebscape.sebcms.persistence.IGenericDao#findAllPaged(java.lang.String, java.lang.Integer, java.lang.Integer)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<T> findAllPaged(String order, Integer offset, Integer limit) {
		log.debug("begin findAllPaged");
		PersistenceManager pm = PersistenceManagerFactoryWrapper.get().getPersistenceManager();
		log.debug("got a PM");
		try {
			pm.currentTransaction().begin();
			String jdoql = "select from " + persistentClass.getName();
			Query query = pm.newQuery( jdoql );
			log.debug( "root: " +  jdoql );
			if( order!= null && !"".equals( order.trim() ) ){
				log.debug( "order: " +  order );
				query.setOrdering( order );
			}
			if( ( offset != null && offset >= 0 )
				  || (limit != null &&  limit >= 0) ){
				log.debug( "paging offset,limit: " +  offset + ", " + limit );
				query.setRange( offset, limit );
			}
			log.debug( "query.toString(): " + query.toString());
			List<T> result = new ArrayList<T>( pm.detachCopyAll( (List<T>)query.execute() ) );
			log.debug("query completed.");
			pm.currentTransaction().commit();
			return result;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//throw new CmsPersistenceException( e.getCause().getMessage() + " jdoQL: " + jdoql );
		} finally {
			pm.close();
		}
		return null;
	}
	
}
